package org.unidata.mdm.rest.v1.data.service;

import java.util.Collection;
import java.util.Objects;
import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.data.context.UpsertRequestContext;
import org.unidata.mdm.data.dto.ErrorInfoDTO;
import org.unidata.mdm.data.dto.UpsertRecordDTO;
import org.unidata.mdm.data.service.DataRecordsService;
import org.unidata.mdm.data.type.data.EtalonRecord;
import org.unidata.mdm.rest.system.ro.details.InfoDetailsRO;
import org.unidata.mdm.rest.system.ro.details.ResultDetailsRO;
import org.unidata.mdm.rest.system.service.AbstractRestService;
import org.unidata.mdm.rest.v1.data.converter.DataRecordEtalonConverter;
import org.unidata.mdm.rest.v1.data.converter.RecordKeysConverter;
import org.unidata.mdm.rest.v1.data.ro.keys.ExternalIdSupport;
import org.unidata.mdm.rest.v1.data.ro.records.AbstractRecordRequestRO;
import org.unidata.mdm.rest.v1.data.ro.records.EtalonRecordRO;
import org.unidata.mdm.rest.v1.data.ro.records.UpsertRecordResultRO;
import org.unidata.mdm.rest.v1.data.ro.records.UpsertRequestRO;
import org.unidata.mdm.rest.v1.data.service.atomic.AtomicDataUpsertRequestRenderer;
import org.unidata.mdm.system.service.RenderingService;
import org.unidata.mdm.system.type.runtime.MeasurementContextName;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;

/**
 * Abstract rest service for working with data
 *
 * @author Alexandr Serov
 * @since 07.10.2020
 **/
public abstract class AbstractDataRestService extends AbstractRestService {

    public static final String DATA_DOMAIN = "DATA";

    public static final String VALID_FROM_PARAM = "validFrom";

    public static final String VALID_TO_PARAM = "validTo";

    public static final String DATE_FOR_PARAM = "dateFor";

    public static final String INCLUDE_DRAFT_PARAM = "includeDraft";

    public static final String INCLUDE_INACTIVE_PARAM = "includeInactive";

    /**
     * Meta model service.
     */
    @Autowired
    protected MetaModelService metaModelService;

    @Autowired
    protected DataRecordsService dataRecordsService;

    @Autowired
    protected RenderingService renderingService;

    private AtomicDataUpsertRequestRenderer atomicDataUpsertRequestRenderer;

    @PostConstruct
    public void init() {
        atomicDataUpsertRequestRenderer = new AtomicDataUpsertRequestRenderer(metaModelService);
    }

    protected UpsertRecordResultRO executeUpsert(UpsertRequestRO input) {
        Objects.requireNonNull(input, "UpsertRequestRO can't be null");
        MeasurementPoint.init(MeasurementContextName.MEASURE_UI_UPDATE);
        MeasurementPoint.start();
        try {
            UpsertRequestContext ctx = atomicDataUpsertRequestRenderer.createUpsertRequestContext(input);
            UpsertRecordResultRO result = new UpsertRecordResultRO();
            UpsertRecordDTO upsertResult = dataRecordsService.upsertRecord(ctx);
            result.setRecordKeys(RecordKeysConverter.to(upsertResult.getRecordKeys()));
            result.setDuplicates(upsertResult.getDuplicateIds());
            result.setDetails(errorsToDetails(upsertResult.getErrors()));
            EtalonRecord resultEtalonRecord = upsertResult.getEtalon();
            EtalonRecordRO ro;
            if (resultEtalonRecord != null && (ro = DataRecordEtalonConverter.to(upsertResult.getEtalon(), upsertResult.getRecordKeys())) != null) {
                result.setDataRecord(ro);
            }
            return result;
        } finally {
            MeasurementPoint.stop();
        }
    }

    protected Long lsnValue(AbstractRecordRequestRO req) {
        return Objects.nonNull(req.getLsn()) ? req.getLsn().getLsn() : null;
    }

    protected Integer shardValue(AbstractRecordRequestRO req) {
        return Objects.nonNull(req.getLsn()) ? req.getLsn().getShard() : null;
    }

    protected String externalIdValue(ExternalIdSupport req) {
        return Objects.nonNull(req.getExternalId()) ? req.getExternalId().getExternalId() : null;
    }

    protected String sourceSystemValue(ExternalIdSupport req) {
        return Objects.nonNull(req.getExternalId()) ? req.getExternalId().getSourceSystem() : null;
    }

    protected ResultDetailsRO errorsToDetails(Collection<ErrorInfoDTO> errors) {
        return updateResultDetails(new ResultDetailsRO(), errors);
    }

    protected ResultDetailsRO updateResultDetails(ResultDetailsRO target, Iterable<ErrorInfoDTO> errors) {
        if (errors != null) {
            for (ErrorInfoDTO error : errors) {
                InfoDetailsRO warn = new InfoDetailsRO();
                warn.setDomain(DATA_DOMAIN);
                warn.setSource(error.getErrorCode());
                warn.setContent(error.getUserMessageDetails());
                target.withWarning(warn);
            }
        }
        return target;
    }

    public void setDataRecordsService(DataRecordsService dataRecordsService) {
        this.dataRecordsService = dataRecordsService;
    }

    public void setRenderingService(RenderingService renderingService) {
        this.renderingService = renderingService;
    }

}
